use std::usize;

use super::vector3::Vec3;

#[derive(Debug, Clone, Copy, PartialEq)]
pub struct Basis {
    rows: [Vec3; 3],
}

impl Default for Basis {
    fn default() -> Self {
        Self {
            rows: [Vec3::X_AXIS, Vec3::Y_AXIS, Vec3::Z_AXIS],
        }
    }
}

impl std::ops::Index<usize> for Basis {
    type Output = Vec3;
    fn index(&self, index: usize) -> &Self::Output {
        &self.rows[index]
    }
}

impl std::ops::IndexMut<usize> for Basis {
    fn index_mut(&mut self, index: usize) -> &mut Self::Output {
        &mut self.rows[index]
    }
}

pub enum EulerOrder {
    XYZ,
    YXZ,
}

impl Basis {
    pub fn from_euler(euler: &Vec3, order: EulerOrder) -> Self {
        todo!()
    }

    pub fn new(
        xx: f32,
        xy: f32,
        xz: f32,
        yx: f32,
        yy: f32,
        yz: f32,
        zx: f32,
        zy: f32,
        zz: f32,
    ) -> Self {
        let mut out = Self::default();
        out.set(xx, xy, xz, yx, yy, yz, zx, zy, zz);
        out
    }

    #[inline(always)]
    fn tdotx(&self, v: &Vec3) -> f32 {
        self[0][0] * v[0] + self[1][0] * v[1] + self[2][0] * v[2]
    }

    #[inline(always)]
    fn tdoty(&self, v: &Vec3) -> f32 {
        self[0][1] * v[0] + self[1][1] * v[1] + self[2][1] * v[2]
    }

    #[inline(always)]
    fn tdotz(&self, v: &Vec3) -> f32 {
        self[0][2] * v[0] + self[1][2] * v[1] + self[2][2] * v[2]
    }

    #[inline(always)]
    pub fn set(
        &mut self,
        xx: f32,
        xy: f32,
        xz: f32,
        yx: f32,
        yy: f32,
        yz: f32,
        zx: f32,
        zy: f32,
        zz: f32,
    ) {
        self[0][0] = xx;
        self[0][1] = xy;
        self[0][2] = xz;
        self[1][0] = yx;
        self[1][1] = yy;
        self[1][2] = yz;
        self[2][0] = zx;
        self[2][1] = zy;
        self[2][2] = zz;
    }

    #[inline(always)]
    pub fn set_columns(&mut self, x: &Vec3, y: &Vec3, z: &Vec3) {
        self.set_column(0, x);
        self.set_column(1, y);
        self.set_column(2, z);
    }

    #[inline(always)]
    pub fn get_column(&self, index: usize) -> Vec3 {
        Vec3::new(self[0][index], self[1][index], self[2][index])
    }

    #[inline(always)]
    pub fn set_column(&mut self, index: usize, value: &Vec3) {
        self[0][index] = value.x;
        self[1][index] = value.y;
        self[2][index] = value.z;
    }

    #[inline(always)]
    pub fn get_main_diagonal(&self) -> Vec3 {
        Vec3::new(self[0][0], self[1][1], self[2][2])
    }

    #[inline(always)]
    pub fn set_zero(&mut self) {
        self[0].zero();
        self[1].zero();
        self[2].zero();
    }

    #[inline(always)]
    pub fn transpose_xform(&self, rhs: &Basis) -> Self {
        Self::new(
            self[0].x * rhs[0].x + self[1].x * rhs[1].x + self[2].x * rhs[2].x,
            self[0].x * rhs[0].y + self[1].x * rhs[1].y + self[2].x * rhs[2].y,
            self[0].x * rhs[0].z + self[1].x * rhs[1].z + self[2].x * rhs[2].z,
            self[0].y * rhs[0].x + self[1].y * rhs[1].x + self[2].y * rhs[2].x,
            self[0].y * rhs[0].y + self[1].y * rhs[1].y + self[2].y * rhs[2].y,
            self[0].y * rhs[0].z + self[1].y * rhs[1].z + self[2].y * rhs[2].z,
            self[0].z * rhs[0].x + self[1].z * rhs[1].x + self[2].z * rhs[2].x,
            self[0].z * rhs[0].y + self[1].z * rhs[1].y + self[2].z * rhs[2].y,
            self[0].z * rhs[0].z + self[1].z * rhs[1].z + self[2].z * rhs[2].z,
        )
    }

    pub fn xform(&self, v: &Vec3) -> Vec3 {
        Vec3::new(self[0].dot(v), self[1].dot(v), self[2].dot(v))
    }

    pub fn xform_inv(&self, v: &Vec3) -> Vec3 {
        Vec3::new(
            (self[0][0] * v.x) + (self[1][0] * v.y) + (self[2][0] * v.z),
            (self[0][1] * v.x) + (self[1][1] * v.y) + (self[2][1] * v.z),
            (self[0][2] * v.x) + (self[1][2] * v.y) + (self[2][2] * v.z),
        )
    }

    pub fn determinant(&self) -> f32 {
        self[0][0] * (self[1][1] * self[2][2] - self[2][1] * self[1][2])
            - self[1][0] * (self[0][1] * self[2][2] - self[2][1] * self[0][2])
            + self[2][0] * (self[0][1] * self[1][2] - self[1][1] * self[0][2])
    }

    pub fn scale(&mut self, scale: &Vec3) {
        self[0][0] *= scale.x;
        self[0][1] *= scale.x;
        self[0][2] *= scale.x;
        self[1][0] *= scale.y;
        self[1][1] *= scale.y;
        self[1][2] *= scale.y;
        self[2][0] *= scale.z;
        self[2][1] *= scale.z;
        self[2][2] *= scale.z;
    }
}

impl std::ops::Mul<Basis> for Basis {
    type Output = Self;
    fn mul(self, rhs: Basis) -> Self::Output {
        Self::new(
            rhs.tdotx(&self[0]),
            rhs.tdoty(&self[0]),
            rhs.tdotz(&self[0]),
            rhs.tdotx(&self[1]),
            rhs.tdoty(&self[1]),
            rhs.tdotz(&self[1]),
            rhs.tdotx(&self[2]),
            rhs.tdoty(&self[2]),
            rhs.tdotz(&self[2]),
        )
    }
}

impl std::ops::MulAssign<Basis> for Basis {
    fn mul_assign(&mut self, rhs: Basis) {
        self.set(
            rhs.tdotx(&self[0]),
            rhs.tdoty(&self[0]),
            rhs.tdotz(&self[0]),
            rhs.tdotx(&self[1]),
            rhs.tdoty(&self[1]),
            rhs.tdotz(&self[1]),
            rhs.tdotx(&self[2]),
            rhs.tdoty(&self[2]),
            rhs.tdotz(&self[2]),
        )
    }
}

impl std::ops::Add<Basis> for Basis {
    type Output = Self;
    fn add(self, rhs: Basis) -> Self::Output {
        let mut out = self;
        out += rhs;
        out
    }
}

impl std::ops::AddAssign<Basis> for Basis {
    fn add_assign(&mut self, rhs: Basis) {
        self[0] += rhs[0];
        self[1] += rhs[1];
        self[2] += rhs[2];
    }
}

impl std::ops::Sub<Basis> for Basis {
    type Output = Self;
    fn sub(self, rhs: Basis) -> Self::Output {
        let mut out = self;
        out -= rhs;
        out
    }
}

impl std::ops::SubAssign<Basis> for Basis {
    fn sub_assign(&mut self, rhs: Basis) {
        self[0] -= rhs[0];
        self[1] -= rhs[1];
        self[2] -= rhs[2];
    }
}

impl std::ops::Mul<f32> for Basis {
    type Output = Self;
    fn mul(self, rhs: f32) -> Self::Output {
        let mut out = self;
        out *= rhs;
        out
    }
}

impl std::ops::MulAssign<f32> for Basis {
    fn mul_assign(&mut self, rhs: f32) {
        self[0] *= rhs;
        self[1] *= rhs;
        self[2] *= rhs;
    }
}
